package mysql

// see: 读写分离 https://gorm.io/zh_CN/docs/dbresolver.html
// see: 自动更新时间字段 https://gorm.io/zh_CN/docs/models.html#%E5%88%9B%E5%BB%BA-%E6%9B%B4%E6%96%B0%E6%97%B6%E9%97%B4%E8%BF%BD%E8%B8%AA%EF%BC%88%E7%BA%B3%E7%A7%92%E3%80%81%E6%AF%AB%E7%A7%92%E3%80%81%E7%A7%92%E3%80%81Time%EF%BC%89
import (
	"errors"
	"fmt"
	baseConfig "gitee.com/loyalflower/toolkit/config"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/schema"
	"sync"
	"time"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/plugin/dbresolver"
)

var (
	connectionsConfig = make(map[string]*Conn)
	connections       = make(map[string]chan *gorm.DB)
	once              sync.Once
)

type (
	Config struct {
		// 连接地址
		Host string `yaml:"host"`
		// 链接端口号
		Port string `yaml:"port"`
		// 数据库,没有则默认为0
		Database string `yaml:"database"`
		// 数据库链接账号
		UserName string `yaml:"username"`
		// 密码，如果没有则为空
		Password    string `yaml:"password"`
		TablePrefix string `yaml:"prefix"`
		// 最大空闲连接数
		MaxOpenConn int `yaml:"maxopenconn"`
		// 最大连接数
		MaxIdleConn int `yaml:"maxidleconn"`
		// 最大连接时间
		MaxLifeTime            time.Duration `yaml:"maxlifetime"`
		Debug                  bool          `yaml:"debug"`
		SkipDefaultTransaction bool          `yaml:"skip_default_transaction"`
		PrepareStmt            bool          `yaml:"prepare_stmt"`
	}

	Conn struct {
		// 主库配置
		MasterConfigs []Config `yaml:"Master"`
		// 从库配置
		SlaveConfigs []Config `yaml:"Slave"`
		sync.Once
	}
)

// Connection 初始化默认连接
// Connection(name) name default is "default"
// Connection(name, filename)
func Connection(args ...string) (connection *gorm.DB) {
	var (
		name     = "default"
		filename = "mysql"
	)

	if l := len(args); l > 1 {
		name = args[0]
		filename = args[1]
	} else if l > 0 {
		name = args[0]
	}

	if connChan, ok := connections[name]; !ok {
		once.Do(func() {
			baseConfig.Get(filename, &connectionsConfig)
			for key, con := range connectionsConfig {
				if c, err := con.Init(); nil == err {
					connections[key] = make(chan *gorm.DB, 1)
					connections[key] <- c
				} else {
					panic(fmt.Sprintf("初始化数据库连接出错：%v", err))
				}
			}
		})
		// 这步很重要
		connection = <-connections[name]
		connections[name] <- connection
	} else {
		connection = <-connChan
		connChan <- connection
	}

	return connection
}

// Init 连接数据库
func (c *Conn) Init() (*gorm.DB, error) {
	if len(c.MasterConfigs) == 0 {
		return nil, errors.New("数据库master的配置不能为空")
	}
	con := c.MasterConfigs[0]
	dsn := formatDSN(con)
	// 设置表名前缀。还需要优化多库连接时，不同表名的处理
	// gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {
	// 	return "prefix_" + defaultTableName
	// }

	config := gorm.Config{
		SkipDefaultTransaction: con.SkipDefaultTransaction,
		PrepareStmt:            con.PrepareStmt,
		NamingStrategy: schema.NamingStrategy{
			TablePrefix:   con.TablePrefix, // 表名前缀，`User` 的表名应该是 `t_users`
			SingularTable: true,            // 使用单数表名，启用该选项，此时，`User` 的表名应该是 `t_user`
		},
	}

	sqlConn, err := gorm.Open(mysql.Open(dsn), &config)
	if nil == err {
		drConfig := dbresolver.Config{}
		if len(c.SlaveConfigs) > 0 || len(c.MasterConfigs) > 1 {
			var (
				master []gorm.Dialector
				slave  []gorm.Dialector
			)
			if len(c.MasterConfigs) > 1 {
				for _, v := range c.MasterConfigs {
					master = append(master, mysql.Open(formatDSN(v)))
				}
			}
			for _, v := range c.SlaveConfigs {
				slave = append(slave, mysql.Open(formatDSN(v)))
			}
			drConfig = dbresolver.Config{
				// `db2` 作为 sources，`db3`、`db4` 作为 replicas
				Sources:  master,
				Replicas: slave,
				// sources/replicas 负载均衡策略
				//Policy: dbresolver.RandomPolicy{},
			}
		}
		resolver := dbresolver.Register(drConfig)
		if con.MaxOpenConn > 0 {
			resolver = resolver.SetMaxOpenConns(con.MaxOpenConn)
		}

		if con.MaxLifeTime > 0 {
			resolver = resolver.SetConnMaxLifetime(con.MaxLifeTime)
		}
		if con.MaxIdleConn > 0 {
			resolver = resolver.SetMaxIdleConns(con.MaxIdleConn)

		}
		if err := sqlConn.Use(resolver); nil != err {
			return nil, fmt.Errorf("数据库连接池配置异常：%w", err)
		}
		if con.Debug {
			sqlConn.Logger = sqlConn.Logger.LogMode(logger.Info)
		}
	} else {
		return nil, fmt.Errorf("failed to connect database,err: %w", err)
	}
	return sqlConn, nil
}

func formatDSN(config Config) string {
	return fmt.Sprintf(
		"%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=true&loc=Local&timeout=%s",
		config.UserName,
		config.Password,
		config.Host,
		config.Port,
		config.Database,
		"3s", // 3s超时
	)
}
